package com.share.common.utils;


import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

@Slf4j
public class JarUtil {


    //key是jar文件,类文件名字,class文件对象 --- 目前没用到
    private static Map<String, ConcurrentHashMap<String, Class>> clazzMap = new ConcurrentHashMap<>();

    //key是jar文件,value是这个jar的类加载器
    private static Map<String, URLClassLoader> classLoaderMap = new ConcurrentHashMap<>();


    /**
     * @description: 从JAR包中加载并保存类
     * @date: 2021/10/22 15:54
     */
    private static URLClassLoader loadClassFromJar(String jarPath, String jarName) throws Exception {
        //返回类加载器
        URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{new URL("file:" + jarPath)});
        ConcurrentHashMap tmpMap = clazzMap.get(jarName);
        if (tmpMap == null || tmpMap.size() == 0) {
            return classLoader;
        }

        JarFile jarFile = new JarFile(jarPath);
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            //获取包的路径名
            JarEntry jarEntry = entries.nextElement();
            String entryName = jarEntry.getName();
            if (entryName.charAt(0) == '/') {
                entryName = entryName.substring(1);
            }
            if (jarEntry.isDirectory() || !entryName.endsWith(".class")) {
                continue;
            }
            String className = entryName.substring(0, entryName.length() - 6);
            className = className.replace("/", ".");

            //加载类
            Class<?> clazz = classLoader.loadClass(className);
            if (clazz != null && !clazz.isInterface()) {
                tmpMap = clazzMap.get(jarName);
                if (tmpMap == null) {
                    tmpMap = new ConcurrentHashMap();
                    clazzMap.put(jarName, tmpMap);
                }
                tmpMap.put(className, clazz);
            }
        }
        jarFile.close();
        return classLoader;
    }

    /**
     * 多参数平铺展开调用这个接口
     * 方法案例参数 public static String abc(Stirng a,int b, Stirng c)
     */
    public static Object executeMethod(String jarPath, String jarName, String clazzName, String methodName, List<Object> params) throws Exception {
        ClassLoader classLoader = null;
        try {
            classLoader = loadClassFromJar(jarPath, jarName);
            Class clazz = clazzMap.get(jarName).get(clazzName);
            List<Class> paramsClazz = params.stream()
                    .map(o -> o == null ? String.class : o.getClass()).collect(Collectors.toList());
            Class[] classes = new Class[paramsClazz.size()];
            paramsClazz.toArray(classes);
            Method method = clazz.getDeclaredMethod(methodName, classes);
            Object res = method.invoke(classLoader, params.toArray());
            return res;
        } finally {
            if (classLoader != null) {
                ((URLClassLoader) classLoader).close();
            }
        }
    }

    public static URLClassLoader loadJar(String jarPath) {
        File jarFile = new File(jarPath);
        Method method = null;
        try {
            method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        } catch (NoSuchMethodException | SecurityException e1) {
            e1.printStackTrace();
        }
        // 获取方法的访问权限以便写回
        boolean accessible = method.isAccessible();
        // 创建线程类加载器-避免同一个java类加载冲突
        URLClassLoader classLoader = classLoaderMap.get(jarPath);
        if (classLoader == null) {
            try {
                method.setAccessible(true);
                URL url = jarFile.toURI().toURL();
                classLoader = new URLClassLoader(new URL[]{url});
                method.invoke(classLoader, url);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                method.setAccessible(accessible);
                classLoaderMap.put(jarPath, classLoader);
            }
        }
        return classLoader;
    }
}
